package com.lry.basic.spring.aop.core;

import com.lry.basic.spring.aop.face.*;
import com.lry.basic.spring.ioc.BeanFactory;
import com.lry.basic.spring.ioc.BeanFactoryAware;
import com.lry.basic.spring.ioc.BeanPostProcessor;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.*;

/**
 * @author:刘仁有
 * @desc:
 * @email:953506233@qq.com
 * @data:2019/11/8
 */
public class AdvisorAutoProxyCreator implements BeanPostProcessor, AdvisorRegistry, BeanFactoryAware {

    private List<Advisor> advisors;

    private BeanFactory beanFactory;

    public AdvisorAutoProxyCreator(){
        this.advisors = new ArrayList<>();
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        List<Advisor>matchAdvisors = this.getMatchedAdvisors(bean,beanName);
        if(!matchAdvisors.isEmpty()){
            bean = this.createProxy(bean,beanName,matchAdvisors);
        }
        return bean;
    }

    private Object createProxy(Object bean, String beanName, List<Advisor> matchAdvisors) {
        return AopProxyFactory.getDefaultAopProxyFactory().createAopProxy(bean, beanName, matchAdvisors, beanFactory)
                .getProxy();
    }

    private List<Advisor> getMatchedAdvisors(Object bean,String beanName){
        if(advisors.isEmpty()){
            return null;
        }
        Class<?> beanClass = bean.getClass();
        List<Method> beanMethods = this.getAllMethodForClass(beanClass);
        List<Advisor> matchAdvisors = new ArrayList<>();
        for(Advisor advisor:this.advisors){
            if(advisor instanceof PointcutAdvisor) {
                if (isPointcutMatchBean((PointcutAdvisor)advisor,beanClass, beanMethods)){
                    matchAdvisors.add(advisor);
                }
            }
        }
        return matchAdvisors;
    }

    private boolean isPointcutMatchBean(PointcutAdvisor advisor, Class<?> beanClass, List<Method> beanMethods) {
        Pointcut pointcut = advisor.getPointcut();
        // 首先判断类是否匹配
        // 注意之前说过的AspectJ情况下这个匹配是不可靠的，需要通过方法来匹配
        //这里的判断仅仅起到过滤作用，类不匹配的前提下直接跳过
        if(!pointcut.matchClass(beanClass)){
            return false;
        }

        for(Method method:beanMethods){
            if(pointcut.matchMethod(method,beanClass)){
                return true;
            }
        }
        return false;
    }

    private List<Method> getAllMethodForClass(Class<?> beanClass) {
        List<Method> allMethods = new LinkedList<>();
        Set<Class<?>> classes = new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(beanClass));
        classes.add(beanClass);
        for (Class<?> clazz : classes) {
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
            for (Method m : methods) {
                allMethods.add(m);
            }
        }
        return allMethods;
    }

    @Override
    public void registerAdvisor(Advisor advisor) {
        if(null==advisor)return;
        advisors.add(advisor);
    }

    @Override
    public List<Advisor> getAdvisors() {
        return advisors;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }
}
